package com.leyou.auth.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.auth.constants.RedisConstants;
import com.leyou.auth.dto.Payload;
import com.leyou.auth.dto.UserDetail;
import com.leyou.common.exception.LyException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


public class JwtUtils {

    /**
     * JWT解析器
     */
    private final JwtParser jwtParser;
    /**
     * 秘钥
     */
    private final SecretKey secretKey;
    //Object提供一些功能将转换成Java对象匹配JSON结构
    private final static ObjectMapper mapper = new ObjectMapper();

    private final StringRedisTemplate redisTemplate;

    public JwtUtils(String key,StringRedisTemplate redisTemplate) {
        // 生成秘钥
        secretKey = Keys.hmacShaKeyFor(key.getBytes(Charset.forName("UTF-8")));
        // JWT解析器
        this.jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();

        this.redisTemplate=redisTemplate;
    }

    /**
     * 生成jwt，用默认的JTI
     *
     * @param userDetail 用户信息
     * @return JWT
     */
    public String createJwt(UserDetail userDetail) {
        return createJwt(userDetail, 1800);
    }

    /**
     * 生成jwt，自己指定的JTI
     *
     * @param userDetail 用户信息
     * @return JWT
     */
    public String createJwt(UserDetail userDetail, int expireSeconds) {

        try {
            //获取token的唯一 jwt id
            String jti=createJti();

            //把user的Id作为redis的key，jti作为redis的value,存储，存半小时，
            redisTemplate.opsForValue().set(
                    RedisConstants.JTI_KEY_PREFIX+userDetail.getId(),
                    jti,
                    RedisConstants.TOKEN_EXPIRE_SECONDS,
                    TimeUnit.SECONDS);

            // 生成token
            String token = Jwts.builder()
                    .signWith(secretKey)
                    .setId(jti) //添加唯一标识符
                    .claim("user", mapper.writeValueAsString(userDetail)) // 添加用户信息
                    .setExpiration(DateTime.now().plusSeconds(expireSeconds).toDate())
                    .compact();

            return token;

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析jwt，并将用户信息转为指定的Clazz类型
     *
     * @param jwt   token
     * @return 载荷，包含JTI和用户信息
     */
    public Payload parseJwt(String jwt) {
        Payload payload=null;
        String jti=null;
        UserDetail userDetail=null;
        try {
            Jws<Claims> claimsJws = jwtParser.parseClaimsJws(jwt);
            Claims claims = claimsJws.getBody();

             payload = new Payload();
             jti=claims.getId();
            payload.setJti(jti);
            userDetail=mapper.readValue(claims.get("user",String.class),UserDetail.class);

        } catch (IOException e) {
            throw new LyException(401,"用户信息解析失败");
        }
        String key= RedisConstants.JTI_KEY_PREFIX+userDetail.getId();
        //如果redis中没有这个key或者redis中的key获取到的jti和解析的不同
        if (!redisTemplate.hasKey(key)|| !jti.equals(redisTemplate.opsForValue().get(key))){
            throw  new LyException(401,"用户持有非法token");
        }
        payload.setUserDetail(userDetail);
        return payload;
    }

    private String createJti() {
        return StringUtils.replace(UUID.randomUUID().toString(), "-", "");
    }

    /**
     * 刷新jwt有效期
     * @param userId 用户id
     */
    public void refreshJwt(Long userId){
        refreshJwt(userId, RedisConstants.TOKEN_EXPIRE_SECONDS);
    }
    /**
     * 刷新jwt有效期
     * @param userId 用户id
     * @param expireSeconds 有效期
     */
    public void refreshJwt(Long userId, int expireSeconds){
        redisTemplate.expire(RedisConstants.JTI_KEY_PREFIX + userId, expireSeconds, TimeUnit.SECONDS);
    }
}